package Agent;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;

import dlife.ga.DoubleGene;
import dlife.nn.FFNeuralNetwork;
import dlife.nn.SigmoidNeuron;
import PacMan.GameModel;
import PacMan.Ghost;
import PacMan.PacMan;
import PacMan.SpeedTiming;
import PacMan.Thing;
import PacMan.World;
import PacMan.Player;

public class Agent {
	World world;
	/** A game model will tell us where everything is */
	GameModel gm = null;
	/** The player which is basically PacMan */
	Player myPlayer = null;
	/** The network that is advising PacMan */
	FFNeuralNetwork network;
	
	//yPlayer.m_gameModel.m_gameState --> is pretty useless, just says type of wall or fruit
	
	int ghostSpeed;
	int pacSpeed;
	// Move types
	Byte STILL = 0;
	Byte UP = 1;
	Byte DOWN = 2;
	Byte LEFT = 3;
	Byte RIGHT = 4;
	PacMan myPacman = null;
	
	public Agent(World world, int ghostSpeed, int pacSpeed) {
		this.world = world;
		this.ghostSpeed = ghostSpeed;
		this.pacSpeed = pacSpeed;
	}
	
	public void setNetwork(FFNeuralNetwork n)
	{
		this.network = n;
	}
	
	public void setPacman(PacMan pac){
		this.myPacman = pac;
	}
	
	public void setGameModel(GameModel gm){
		this.gm = gm;
		this.myPlayer = gm.m_player;
	}
	
	
	/**
	 * Instead of passing it a neural network, we do this
	 */
	public void makeOwnNetwork(){
		int[] layers = new int[]{24 + 9,5,4};
		BufferedReader input = null;
		double[] weights = new double[SpeedTiming.NUM_GENES];
		 try {
			input =  new BufferedReader(
					 new FileReader("best.txt"));
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			String line = input.readLine();
			line = input.readLine();
			String temp[] = line.split(",");
			for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
				weights[i] = Double.parseDouble(temp[i]);
			}
			System.out.println("load scuccessful");
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		 
		// load the weights from the file
				
		FFNeuralNetwork network = new FFNeuralNetwork(layers, weights, new SigmoidNeuron());
		this.setNetwork(network);
	}
	
	/*
	 * This calculates if the current tile is on the board
	 */
	public boolean isValidX(int x, int xOffSet){
		if(x+xOffSet >=0 && x+xOffSet <28)
			return true;
		return false;
	}
	
	/*
	 * This calculates if the current tile is on the board
	 */
	public boolean isValidY(int y, int yOffSet){
		if(y+yOffSet >=0 && y+yOffSet <31)
			return true;
		return false;
	}
	
	/**
	 * Called when the world is updated, returns a requested move.
	 */
	public Byte tick() { 
		int x = myPlayer.m_locX;
		int y = myPlayer.m_locY;
		if(this.gm.m_pacMan.m_globalTickCount <= 5)
			System.out.println(x + "  " + y);
		double dir = myPlayer.m_direction;
		
		
		double[][]tVal = new double[7][7];	// where agent is in the center
		for(int r=0; r<7;r++){
			for(int c=0;c<7;c++){
				if(this.isValidX(x, r-3) && this.isValidY(y, c-3) && world.tiles[x+r-3][y+c-3])
					tVal[r][c] = 1;
			}
		}
		
		Ghost[]  ghosts = this.gm.m_ghosts;  
		int[][] posG = new int[4][2];
		posG[0][0] = ghosts[0].m_locX;
		posG[0][1] = ghosts[0].m_locY;
		posG[1][0] = ghosts[1].m_locX;
		posG[1][1] = ghosts[1].m_locY;
		posG[2][0] = ghosts[2].m_locX;
		posG[2][1] = ghosts[2].m_locY;
		posG[3][0] = ghosts[3].m_locX;
		posG[3][1] = ghosts[3].m_locY;
		
		ghostDist[] gDist = new ghostDist[4]; // holds the manhattan distance of the ghosts
		for(int i=0; i<4; i++){
			gDist[i] = new ghostDist(posG[i][0], posG[i][1], i, Math.abs(x-posG[i][0]) + Math.abs(y-posG[i][1]), ghosts[i].m_direction);
		}
		// now we need to sort these positions
		//Arrays.sort(gDist);

		// now feed into the network anything that is valid
		double[] Ninputs = new double[24 + 9 + 4]; // 24 surrounding tiles, 8 for ghosts, 1 for direction and 4 ghost dir
		int posCounter = 0;
		Ninputs[posCounter++] = dir;
		int MAX_D = 20;	// don't care about ghosts more than 5 away
		for(int i=0; i<4; i++){
			if(x-gDist[i].x > MAX_D)
				Ninputs[posCounter++] = MAX_D;
			else if(x-gDist[i].x < -MAX_D)
				Ninputs[posCounter++] = -MAX_D;
			else
				Ninputs[posCounter++] = x - gDist[i].x;
			
			if(y-gDist[i].y > MAX_D)
				Ninputs[posCounter++] = MAX_D;
			else if(y-gDist[i].y < -MAX_D)
				Ninputs[posCounter++] = -MAX_D;
			else
				Ninputs[posCounter++] = y - gDist[i].y;
			Ninputs[posCounter++] = gDist[i].dir;
		}
		
		for(int r=0; r<7;r++){
			for(int c=0;c<7;c++){
				if(Math.abs(r-3) + Math.abs(c-3) <= 3 && Math.abs(r-3) + Math.abs(c-3) != 0 ){
					
					Ninputs[posCounter] = tVal[r][c];
					posCounter++;
				}
			}
		}
		
		
		network.setInputs(Ninputs);
		network.propagate();
		double[] outputs = network.getRawOutputs();
		
		/*System.out.println("Column " + myPlayer.m_locX + " Row " + myPlayer.m_locY + " direction "
				+ myPlayer.m_direction);
		*/
		if(true)
			return (byte)(1 + findMax(outputs));
		
		
		/*if (myPlayer.m_direction == this.STILL){
			if (world.tiles[myPlayer.m_locX+this.getIncrementByDirection(this.RIGHT, 0)]
		               [myPlayer.m_locY+this.getIncrementByDirection(this.RIGHT, 1)])
				return this.RIGHT;
			if (world.tiles[myPlayer.m_locX+this.getIncrementByDirection(this.LEFT, 0)]
			               [myPlayer.m_locY+this.getIncrementByDirection(this.LEFT, 1)])
					return this.LEFT;
			return this.UP;
		}
		if(world.tiles[myPlayer.m_locX+this.getIncrementByDirection(myPlayer.m_direction, 0)]
		               [myPlayer.m_locY+this.getIncrementByDirection(myPlayer.m_direction, 1)])
			return myPlayer.m_direction;
		*/
		
		return new Double(Math.floor((Math.random() * 5))).byteValue();
	}
	
	
	/* 
     * Method to takes a double value array and finds out
     * which index has the greatest value
     * @author DLink library sample code
     */
    private byte findMax(double[] values) {
            double max = 0;
            double challenger;
            byte returnValue = 0;
            for(byte i = 0; i < values.length; i++) {
                    challenger = values[i];
                    if(challenger > max) {
                            max = challenger;
                            returnValue = (byte) (i+1);
                    }
            }
            //System.out.println(returnValue);
            return returnValue;
    }
	
	/**
	 * This returns the change in the x/y direction when facing currDir
	 * @param currDir
	 * @param xyDir 0 for x and 1 for y
	 * @return
	 */
	public int getIncrementByDirection(byte currDir, int xyDir){
		if(currDir == this.UP && xyDir == 1)
			return 1;
		else if(currDir == this.UP)
			return 0;
		if(currDir == this.DOWN && xyDir == 1)
			return -1;
		else if(currDir == this.DOWN)
			return 0;
		if(currDir == this.RIGHT && xyDir == 0)
			return 1;
		else if(currDir == this.RIGHT)
			return 0;
		if(currDir == this.LEFT && xyDir == 0)
			return -1;
		else if(currDir == this.LEFT)
			return 0;
		return 0;
	}
}
